/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 combat_actor.cpp

	$Header: /heroes4/combat_actor.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "combat_actor.h"

#include <cassert>

#include "actor_sequence.h"
#include "battlefield.h"
#include "combat_actor_action.h"
#include "combat_actor_model.h"
#include "combat_label.h"
#include "combat_object_type.h"
#include "combat_object_visitor.h"
#include "combat_reader.h"
#include "creature_stack.h"
#include "direction.h"
#include "play_combat_animation.h"
#include "random.h"
#include "screen_point.h"
#include "threat_footprint.h"

// ------------------------------------------------------------------------------
// t_combat_actor members
// ------------------------------------------------------------------------------
t_combat_actor::t_combat_actor( t_battlefield* battlefield ) 
			  : t_abstract_combat_object( battlefield )
{
	initialize();
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_combat_actor::t_combat_actor( t_battlefield*				battlefield,
								t_cached_ptr< t_model >		model_ptr,
							    t_combat_actor_action_id	action,
								t_direction					direction )
	: t_abstract_combat_object( battlefield ),	m_model_ptr( model_ptr )
{
	assert( m_model_ptr.get() != 0 );
	m_current_action_id = action;
	m_current_direction = direction;
	initialize();
}

void t_combat_actor::initialize()
{
	m_brightness = 256;
	m_hue_target = 0;
	m_hue_delta = 0;
	m_saturation = 256;
	m_shadow_enabled = true;
	m_current_frame_num = 0;
}

void t_combat_actor::set_model( t_cached_ptr< t_model > model_ptr )
{
	m_model_ptr = model_ptr;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_combat_actor::~t_combat_actor()
{
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_combat_actor::draw_to( t_uint32				current_time,
							  t_screen_rect const&	source_rect,
							  t_abstract_bitmap16&	dest,
							  t_screen_point const&	dest_point ) const
{
	if (m_current_image.get() != 0)
	{
		m_model_ptr->draw_to( m_current_image.get(), source_rect, dest, 
		                      dest_point + m_current_image_offset, get_alpha() );
		return;
	}
	m_model_ptr->draw_to( m_current_action_id, m_current_direction, m_current_frame_num,
						  source_rect, dest, dest_point, get_alpha() );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_combat_actor::draw_to(
	t_uint32				current_time,
	t_abstract_bitmap16 &	dest,
	t_screen_point const &	dest_point ) const
{
	if (m_current_image.get() != 0)
	{
		m_model_ptr->draw_to( m_current_image.get(), dest, 
		                      dest_point + m_current_image_offset, get_alpha() );
		return;
	}
	m_model_ptr->draw_to( m_current_action_id, m_current_direction, m_current_frame_num,
						  dest, dest_point, get_alpha() );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_combat_actor::draw_shadow_to(
	t_uint32				current_time,
	t_screen_rect const &	source_rect,
	t_abstract_bitmap16 &	dest,
	t_screen_point const &	dest_point ) const
{
	if (!m_shadow_enabled)
		return;
	m_model_ptr->draw_shadow_to(
		m_current_action_id,
		m_current_direction,
		m_current_frame_num,
		source_rect,
		dest,
		dest_point, get_alpha() );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_combat_actor::draw_shadow_to(
	t_uint32				current_time,
	t_abstract_bitmap16 &	dest,
	t_screen_point const &	dest_point ) const
{
	if (!m_shadow_enabled)
		return;
	m_model_ptr->draw_shadow_to(
		m_current_action_id,
		m_current_direction,
		m_current_frame_num,
		dest,
		dest_point, get_alpha() );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
int t_combat_actor::get_footprint_size() const
{
	return m_model_ptr->get_footprint_size();
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
int t_combat_actor::get_prewalk_distance() const
{
	return m_model_ptr->get_prewalk_distance();
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
int t_combat_actor::get_postwalk_distance() const
{
	return m_model_ptr->get_postwalk_distance();
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
int t_combat_actor::get_walk_distance() const
{
	return m_model_ptr->get_walk_distance();
}

// ------------------------------------------------------------------------------
// return a count of the # of frames in the current action
// ------------------------------------------------------------------------------
int t_combat_actor::get_frame_count() const
{
	return m_model_ptr->get_frame_count( m_current_action_id, m_current_direction );
}

// ------------------------------------------------------------------------------
// return a count of the # of frames in the current action
// ------------------------------------------------------------------------------
int t_combat_actor::get_key_frame( t_action_id action ) const
{
	return m_model_ptr->get_key_frame( action );
}

// ------------------------------------------------------------------------------
// return a count of the # of frames for a given action
// ------------------------------------------------------------------------------
int t_combat_actor::get_frame_count( t_action_id action ) const
{
	return m_model_ptr->get_frame_count( action, m_current_direction );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
int t_combat_actor::get_frames_per_second() const
{
	return m_model_ptr->get_frames_per_second( m_current_action_id );
}

// ------------------------------------------------------------------------------
// get frame rate for a given action
// ------------------------------------------------------------------------------
int t_combat_actor::get_frames_per_second( t_combat_actor_action_id action ) const
{
	return m_model_ptr->get_frames_per_second( action );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
int t_combat_actor::get_height() const
{
	return m_model_ptr->get_height();
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_combat_actor::t_model const & t_combat_actor::get_model() const
{
	return *m_model_ptr;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_screen_rect t_combat_actor::get_rect() const
{
	return m_model_ptr->get_rect( m_current_action_id, m_current_direction, m_current_frame_num );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_screen_rect t_combat_actor::get_rect( t_uint32 current_time ) const
{
	return m_model_ptr->get_rect( m_current_action_id, m_current_direction, m_current_frame_num );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_screen_rect t_combat_actor::get_shadow_rect() const
{
	return m_model_ptr->get_rect( m_current_action_id, m_current_direction, m_current_frame_num );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_screen_rect t_combat_actor::get_shadow_rect( t_uint32 current_time ) const
{
	return m_model_ptr->get_shadow_rect( m_current_action_id, m_current_direction, 
		                                 m_current_frame_num );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
bool t_combat_actor::has_action( t_action_id action_id, t_direction direction ) const
{
	return m_model_ptr->has_action( action_id, direction );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
bool t_combat_actor::hit_test( t_uint32 current_time, t_screen_point const & point ) const
{
	return m_model_ptr->hit_test( m_current_action_id, m_current_direction, m_current_frame_num, point );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
bool t_combat_actor::is_animated() const
{
	return true;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
bool t_combat_actor::is_underlay() const
{
	return false;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
bool t_combat_actor::needs_redrawing( t_uint32 last_update_time, t_uint32 current_time ) const
{
	return false;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_combat_actor::set_current_action( t_action_id new_action_id, int new_frame_num )
{
	assert( new_action_id >= 0 && new_action_id < k_combat_actor_action_count );
	m_current_action_id = new_action_id;
	m_current_frame_num = new_frame_num;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_combat_actor::set_current_direction( t_direction new_direction )
{
	int frame_count;

	assert( new_direction >= 0 && new_direction < k_direction_count );
	m_current_direction = new_direction;
	
	frame_count = get_frame_count();
	if (m_current_frame_num >= frame_count)
		m_current_frame_num = frame_count - 1;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_combat_actor::set_current_frame_num( int new_frame_num )
{
	assert( new_frame_num >= 0 && new_frame_num < get_frame_count() );

	m_current_frame_num = new_frame_num;
}

// ------------------------------------------------------------------------------
// begin walking
// ------------------------------------------------------------------------------
void t_combat_actor::set_animation( t_counted_idle_processor* animation )
{
	m_animation = animation;
}

// ------------------------------------------------------------------------------
// check if actor is dead - if it is, it doesn't block movement.
// ------------------------------------------------------------------------------
bool t_combat_actor::blocks_movement() const
{
	return false;
}


// ------------------------------------------------------------------------------
// return sequence cache
// ------------------------------------------------------------------------------
t_actor_sequence_cache t_combat_actor::get_cache( t_action_id action, t_direction direction ) const
{
	return m_model_ptr->get_sequence_cache( action, direction );
}

// ------------------------------------------------------------------------------
// set color
// ------------------------------------------------------------------------------
void t_combat_actor::set_color( int hue, int hue_delta, int saturation, int brightness )
{
	if (hue == m_hue_target && hue_delta == m_hue_delta 
		&& saturation == m_saturation && brightness == m_brightness )
		return;
	m_hue_target = hue;
	m_hue_delta = hue_delta;
	m_brightness = brightness;
	if (hue_delta == 0 && brightness == 256 && saturation == 256)
	{
		m_saturation = saturation;
		m_current_image.reset();
		return;
	}
	if (hue_delta == 0 && brightness == 256)
	{
		set_saturation( saturation );
		return;
	}
	m_saturation = saturation;

	t_actor_sequence_ptr sequence = m_model_ptr->get_sequence_ptr( m_current_action_id, 
		                                                           m_current_direction );

	t_bitmap_layer const* image;

	image = sequence->get_frame_ptr( m_current_frame_num );
	m_current_image.reset( image->adjust_color( hue, hue_delta, saturation, brightness ));
	m_current_image_offset = sequence->get_offset();
}

// ------------------------------------------------------------------------------
// set saturation level
// ------------------------------------------------------------------------------
void t_combat_actor::set_saturation( int saturation )
{
	if (saturation == m_saturation)
		return;
	if (m_hue_delta != 0 || m_brightness != 256)
	{
		set_color( m_hue_target, m_hue_delta, saturation, m_brightness );
		return;
	}
	m_saturation = saturation;
	if (m_saturation == 256)
	{
		m_current_image.reset();
		return;
	}
	t_actor_sequence_ptr sequence = m_model_ptr->get_sequence_ptr( m_current_action_id, 
		                                                           m_current_direction );

	t_bitmap_layer const* image;

	image = sequence->get_frame_ptr( m_current_frame_num );
	m_current_image.reset( image->adjust_saturation( saturation ) );
	m_current_image_offset = sequence->get_offset();
}

// ------------------------------------------------------------------------------
// get missile offset
// ------------------------------------------------------------------------------
t_map_point_3d t_combat_actor::get_missile_offset() const
{
	return m_model_ptr->get_missile_offset( m_current_direction ) + get_position();
}

// -----------------------------------------------------------------------
// get the center of a creature's body
// -----------------------------------------------------------------------
t_map_point_3d t_combat_actor::get_body_center() const
{
	t_map_point_3d center = get_footprint_center();

	center.height += get_height() / 2;
	return center;
}

// ------------------------------------------------------------------------------
// get spell offset
// ------------------------------------------------------------------------------
t_map_point_3d t_combat_actor::get_spell_origin() const
{
	t_map_point_3d result = get_body_center();
	int			   distance = get_footprint_size() << (k_battlefield_subcell_shift - 1);

	return result + get_direction_offset( m_current_direction ) * distance;
}

// ------------------------------------------------------------------------------
// actors can be moved and removed
// ------------------------------------------------------------------------------
bool t_combat_actor::is_permanent() const
{
	return false;
}


static int const k_current_version = 0;

bool t_combat_actor::read( t_combat_reader& reader )
{
	std::streambuf& stream = reader.get_stream();
	int				version = get<t_uint16>( stream );

	if (version < 0 || version > k_current_version )
		return false;
	if (!read_data( stream ))
		return false;
	m_brightness = get<t_uint16>( stream );
	m_current_action_id = t_action_id( get<t_int8>( stream ));
	m_current_direction = t_direction( get<t_int8>( stream ));
	m_current_frame_num = get<t_int8>( stream );
	m_current_image.reset();
	m_hue_delta = get<t_int16>( stream );
	m_hue_target = get<t_int16>( stream );
	m_saturation = get<t_int16>( stream );
	m_shadow_enabled = get<t_uint8>( stream ) != 0;
	// note: model should be read in derived classes.
	// "current image" should also be set there.
	return true;
}

bool t_combat_actor::write( t_combat_writer& writer ) const
{
	std::streambuf& stream = writer.get_stream();

	put<t_uint16>( stream, k_current_version );
	if (!write_data( stream ))
		return false;
	put<t_uint16>( stream, m_brightness );
	put<t_int8>( stream, m_current_action_id );
	put<t_int8>( stream, m_current_direction );
	put<t_int8>( stream, m_current_frame_num );
	put<t_int16>( stream, m_hue_delta );
	put<t_int16>( stream, m_hue_target );
	put<t_int16>( stream, m_saturation );
	put<t_uint8>( stream, m_shadow_enabled );
	return true;
}

t_combat_object_type t_unsaved_combat_actor::get_object_type() const
{
	return k_combat_object_type_unsaved;
}

bool t_unsaved_combat_actor::read( t_combat_reader& reader )
{
	assert( false );
	return false;
}

bool t_unsaved_combat_actor::write( t_combat_writer& writer ) const
{
	assert( false );
	return false;
}

